/*---------------------------------------------------------------------------------------
--	SOURCE FILE:		select_svr.c -   A simple select based echo server.
--
--	PROGRAM:		select.exe
--
--	FUNCTIONS:		Berkeley Socket API
--
--	DATE:			February 22, 2012
--
--	REVISIONS:		
--
--	DESIGNERS:		Based on Aman Abdulla's modification & redesign of Richard Sevens Example, p165-166
--				Modification & redesign on February 22, 2012
--				
--	PROGRAMMER:		Bryce Watson
--
--	NOTES:
--	The program will accept TCP connections from multiple client machines.
-- 	The program will read data from each client and echo it it back.
--      *new* The program will report interesting statistics (Total # of connections, total concurrent connections, etc)
--	*new* The program will allow clients to maintain concurrent connections & echo multiple times.
---------------------------------------------------------------------------------------*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>

#define SERVER_TCP_PORT 9090	// Default port
#define BUFLEN	52		//Buffer length
#define TRUE	1
#define LISTENQ	5000
#define MAXLINE 4096

// Function Prototypes
static void SystemFatal(const char* );

int main (int argc, char **argv)
{
	int total_clients = 0;
	int current_clients = 0;
	int i, maxi, nready, bytes_to_read, arg;
	int listen_sd, new_sd, sockfd, client_len, port, maxfd, client[FD_SETSIZE];
	struct sockaddr_in server, client_addr;
	char *bp, buf[BUFLEN];
   	ssize_t n;
   	fd_set rset, allset;
	struct timeval tv;
	int bak, new;

	switch(argc)
	{
		case 1:
			port = SERVER_TCP_PORT;	// Use the default port
		break;
		case 2:
			port = atoi(argv[1]);	// Get user specified port
		break;
		case 3:
			port = atoi(argv[1]);
			// Redirect stdout to file.
			fflush(stdout);
			bak = dup(1);//can reset it later.
			new = open(argv[2], "w");
			dup2(new,1);
			close(new);
		break;
		default:
			fprintf(stderr, "Usage: %s [port]\n", argv[0]);
			exit(1);
		break;
	}

	// Create a stream socket
	if ((listen_sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		SystemFatal("Cannot Create Socket!");
	
	// set SO_REUSEADDR so port can be resused imemediately after exit, i.e., after CTRL-c
        arg = 1;
        if (setsockopt (listen_sd, SOL_SOCKET, SO_REUSEADDR, &arg, sizeof(arg)) == -1)
                SystemFatal("setsockopt");

	// Bind an address to the socket
	bzero((char *)&server, sizeof(struct sockaddr_in));
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr = htonl(INADDR_ANY); // Accept connections from any client

	if (bind(listen_sd, (struct sockaddr *)&server, sizeof(server)) == -1)
		SystemFatal("bind error");
	
	// Listen for connections
	// queue up to LISTENQ connect requests
	listen(listen_sd, LISTENQ);

	maxfd	= listen_sd;	// initialize
   	maxi	= -1;		// index into client[] array

	for (i = 0; i < FD_SETSIZE; i++)
           	client[i] = -1;             // -1 indicates available entry
 	FD_ZERO(&allset);
   	FD_SET(listen_sd, &allset);


	while (TRUE)
	{
   		rset = allset;               // structure assignment
		nready = select(maxfd + 1, &rset, NULL, NULL, NULL);

      		if (FD_ISSET(listen_sd, &rset)) // new client connection
      		{
			client_len = sizeof(client_addr);
			if ((new_sd = accept(listen_sd, (struct sockaddr *) &client_addr, &client_len)) == -1)
				SystemFatal("accept error");
			total_clients = total_clients + 1;
			current_clients = current_clients + 1;	
                        gettimeofday(&tv, NULL);
			printf("%ld.%ld,%i,%i,%s,%d\n",tv.tv_sec,tv.tv_usec, total_clients, current_clients, inet_ntoa(client_addr.sin_addr),client_addr.sin_port);
                        fflush(stdout);
			for (i = 0; i < FD_SETSIZE; i++)
			if (client[i] < 0)
            		{
				client[i] = new_sd;	// save descriptor
				break;
            		}
			if (i == FD_SETSIZE)
         		{
				printf ("Too many clients\n");
            			exit(1);
    			}

			FD_SET (new_sd, &allset);     // add new descriptor to set
			if (new_sd > maxfd)
				maxfd = new_sd;	// for select

			if (i > maxi)
				maxi = i;	// new max index in client[] array

			if (--nready <= 0)
				continue;	// no more readable descriptors
     		 }

		for (i = 0; i <= maxi; i++)	// check all clients for data
     		{
			if ((sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset))
         		{
         			//clear the buffer
				memset(&buf[0], 0, sizeof(buf));
				bp = buf;
				bytes_to_read = BUFLEN;
				n = read(sockfd, bp, bytes_to_read);
				//int z = strcmp(buf, "Finished");
				//printf("n=%i\n",n);
				write(sockfd, buf, BUFLEN);   // echo to client
				if( n == -1) {
					gettimeofday(&tv,NULL);
                                	printf("%i,dropped,%s\n",strerror(errno),inet_ntoa(client_addr.sin_addr));
					fflush(stdout);
					close(sockfd);
					current_clients = current_clients -1;
					FD_CLR(sockfd, &allset);
					client[i] = -1;
                                }	
				if (n == 0) // connection closed by client
            			{
					close(sockfd);
					current_clients = current_clients - 1;
					FD_CLR(sockfd, &allset);
               				client[i] = -1;
            			}
				
				if (--nready <= 0)
            				break;        // no more readable descriptors
			}
     		 }
		// Check all the connections for data to send..
   	}
	return(0);
}

// Prints the error stored in errno and aborts the program.
static void SystemFatal(const char* message)
{
    perror (message);
    exit (EXIT_FAILURE);
}
